package com.example.mykotlin.base.util

import java.text.SimpleDateFormat
import java.time.Instant
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.ZoneOffset
import java.time.format.DateTimeFormatter
import java.util.Date
import java.util.concurrent.ConcurrentHashMap
import org.apache.commons.lang3.StringUtils
import org.springframework.cglib.beans.BeanCopier

class BeanCopierUtil {

    companion object {

        /**
         * BeanCopier的缓存
         */
        private val BEAN_COPIER_CACHE = ConcurrentHashMap<String, BeanCopier>()

        /**
         * 生成key
         *
         * @param srcClazz 源文件的class
         * @param tgtClazz 目标文件的class
         * @return string
         */
        private fun genKey(srcClazz: Class<out Any>, tgtClazz: Class<out Any>, flag: String = "") =
            srcClazz.getName() + tgtClazz.getName() + flag


        /**
         * BeanCopier的copy
         *
         * @param source 源文件的
         * @param target 目标文件
         */
        fun <T : Any> copy(source: T): T {
            val key = genKey(source.javaClass, source.javaClass)
            val beanCopier: BeanCopier
            if (BEAN_COPIER_CACHE.containsKey(key)) {
                beanCopier = BEAN_COPIER_CACHE[key]!!
            } else {
                beanCopier = BeanCopier.create(source.javaClass, source.javaClass, false);
                BEAN_COPIER_CACHE[key] = beanCopier
            }
            val newTarget = source.javaClass.getDeclaredConstructor().newInstance()
            beanCopier.copy(source, newTarget, null)
            return newTarget
        }

        /**
         * BeanCopier的copy
         *
         * @param source 源文件的
         * @param target 目标文件
         */
        fun allHomonymSet(source: Any, target: Any) {
            val key = genKey(source.javaClass, target.javaClass)
            val beanCopier: BeanCopier
            if (BEAN_COPIER_CACHE.containsKey(key)) {
                beanCopier = BEAN_COPIER_CACHE[key]!!
            } else {
                beanCopier = BeanCopier.create(source.javaClass, target.javaClass, false);
                BEAN_COPIER_CACHE[key] = beanCopier
            }
            beanCopier.copy(source, target, null)
        }

        /**
         * BeanCopier的copy
         *
         * @param source 源文件的
         * @param target 目标文件
         */
        fun <T : Any> to(source: Any, target: Class<T>): T {
            val key = genKey(source.javaClass, target)
            val beanCopier: BeanCopier
            if (BEAN_COPIER_CACHE.containsKey(key)) {
                beanCopier = BEAN_COPIER_CACHE[key]!!
            } else {
                beanCopier = BeanCopier.create(source.javaClass, target, false);
                BEAN_COPIER_CACHE[key] = beanCopier
            }
            val newTarget = target.getDeclaredConstructor().newInstance()
            beanCopier.copy(source, newTarget, null)
            return newTarget
        }

        /**
         * BeanCopier的copy
         *
         * @param source 源文件的
         * @param target 目标文件
         */
        fun setNotNull(source: Any, target: Any) {
            val key = genKey(source.javaClass, target.javaClass, "notnull")
            val beanCopier: BeanCopier
            if (BEAN_COPIER_CACHE.containsKey(key)) {
                beanCopier = BEAN_COPIER_CACHE[key]!!
            } else {
                beanCopier = BeanCopier.create(source.javaClass, target.javaClass, true);
                BEAN_COPIER_CACHE[key] = beanCopier
            }
            val javaClass = target.javaClass
            val formatStr = "yyyy-MM-dd HH:mm:ss"
            val dateTimeFormatter = DateTimeFormatter.ofPattern(formatStr)
            val dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd")
            beanCopier.copy(source, target) { v, t, c ->
                if (v == null || StringUtils.isBlank(v.toString())) {
                    tryReturn {
                        val f =
                            javaClass.getDeclaredField(c.toString().substring(3).replaceFirstChar { it.lowercase() })
                        f.setAccessible(true)
                        f.get(target)
                    }
                } else {
                    if (v.javaClass == t) v
                    else tryReturn {
                        when (t) {
                            Integer::class.java -> {
                                when (v) {
                                    is Number -> v.toInt()
                                    is String -> v.toInt()
                                    is Date -> v.time.toInt()
                                    is LocalDateTime -> v.toInstant(ZoneOffset.of("+8")).toEpochMilli().toInt()
                                    is LocalDate -> v.atStartOfDay().toInstant(ZoneOffset.of("+8")).toEpochMilli()
                                        .toInt()

                                    else -> null
                                }
                            }

                            Long::class.java -> {
                                when (v) {
                                    is Number -> v.toLong()
                                    is String -> v.toLong()
                                    is Date -> v.time
                                    is LocalDateTime -> v.toInstant(ZoneOffset.of("+8")).toEpochMilli()
                                    is LocalDate -> v.atStartOfDay().toInstant(ZoneOffset.of("+8")).toEpochMilli()
                                    else -> null
                                }
                            }

                            Double::class.java -> {
                                when (v) {
                                    is Number -> v.toDouble()
                                    is String -> v.toDouble()
                                    is Date -> v.time.toDouble()
                                    is LocalDateTime -> v.toInstant(ZoneOffset.of("+8")).toEpochMilli().toDouble()
                                    is LocalDate -> v.atStartOfDay().toInstant(ZoneOffset.of("+8")).toEpochMilli()
                                        .toDouble()

                                    else -> null
                                }
                            }

                            Date::class.java -> {
                                when (v) {
                                    is Number -> Date(v.toLong())
                                    is String -> {
                                        try {
                                            Date(v.toLong())
                                        } catch (_: Exception) {
                                            tryReturn {
                                                SimpleDateFormat(formatStr).parse(v)
                                            }
                                        }
                                    }

                                    is LocalDateTime -> Date.from(v.toInstant(ZoneOffset.of("+8")))
                                    is LocalDate -> Date.from(v.atStartOfDay().toInstant(ZoneOffset.of("+8")))
                                    else -> null
                                }
                            }

                            LocalDateTime::class.java -> {
                                when (v) {
                                    is Number -> LocalDateTime.ofInstant(
                                        Instant.ofEpochMilli(v.toLong()), ZoneOffset.of("+8")
                                    )

                                    is String -> {
                                        try {
                                            LocalDateTime.ofInstant(
                                                Instant.ofEpochMilli(v.toLong()), ZoneOffset.of("+8")
                                            )
                                        } catch (_: Exception) {
                                            tryReturn {
                                                dateTimeFormatter.parse(v)
                                            }
                                        }
                                    }

                                    is LocalDate -> v.atStartOfDay()
                                    else -> null
                                }
                            }

                            LocalDate::class.java -> {
                                when (v) {
                                    is Number -> LocalDate.ofInstant(
                                        Instant.ofEpochMilli(v.toLong()), ZoneOffset.of("+8")
                                    )

                                    is String -> {
                                        try {
                                            LocalDate.ofInstant(
                                                Instant.ofEpochMilli(v.toLong()), ZoneOffset.of("+8")
                                            )
                                        } catch (_: Exception) {
                                            tryReturn {
                                                dateFormatter.parse(v)
                                            }
                                        }
                                    }

                                    is LocalDateTime -> v.toLocalDate()
                                    else -> null
                                }
                            }

                            String::class.java -> v.toString()
                            else -> null
                        }
                    }
                }
            }
        }
    }

}

